package minicat.catalina;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import minicat.catalina.components.*;
import minicat.coyote.components.EndPoint;
import minicat.coyote.components.Processor;
import minicat.servlet.api.HttpServlet;
import minicat.utils.DiskClassLoader;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

/**
 * Minicat的主类
 */
public class Bootstrap {

    private static String pathServer = "conf/server.xml";
    private static String classes = "\\WEB-INF\\classes";
    private Server server;
    private List<EndPoint> endPoints;

    /**
     * Minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        //加载解析Server.xml
        initServer();
        //加载连接器Socket
        initCoyote();
        //加载解析web.xml
        initWebapps();

        while(true) {
            Socket socket = this.endPoints.get(0).getSocket().accept();
            Processor processor = new Processor(socket, server);
            poolInstance().execute(processor);
        }
    }

    private void initCoyote() {
        this.endPoints = new ArrayList<EndPoint>();
        server.getServices().stream().forEach(service -> {
            service.getConnectors().stream().forEach(connector -> {
                try{
                    ServerSocket serverSocket = new ServerSocket(connector.getPort());
                    EndPoint endPoint = new EndPoint();
                    endPoint.setPort(connector.getPort());
                    endPoint.setSocket(serverSocket);
                    this.endPoints.add(endPoint);
                    System.out.println("=====>>>Minicat start on port：" + connector.getPort());
                }catch (Exception e){
                    throw new RuntimeException("Socket 异常："+ ExceptionUtil.stacktraceToString(e));
                }
            });
        });


    }


    private void initServer() {
        this.server = new Server();
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(pathServer);
        SAXReader saxReader = new SAXReader();
        try{
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            Element serverEle = (Element) rootElement.selectSingleNode("//Server");
            server.setPort(serverEle.attributeValue("port"));
            server.setShutdown(serverEle.attributeValue("shutdown"));
            server.setServices(new ArrayList<Service>());
            List<Element> serviceEles = serverEle.selectNodes("Service");
            serviceEles.stream().forEach(serviceEle->{
                serviceEle = (Element) serviceEle;
                Service service = new Service();
                service.setName(serviceEle.attributeValue("name"));
                service.setConnectors(new ArrayList<Connector>());
                List<Element> connectorEles = serviceEle.selectNodes("Connector");
                connectorEles.stream().forEach(connectorEle->{
                    Connector connector = new Connector();
                    connector.setPort(Integer.parseInt(connectorEle.attributeValue("port")));
                    connector.setConnectionTimeout(connectorEle.attributeValue("connectionTimeout"));
                    connector.setProtocol(connectorEle.attributeValue("protocol"));
                    connector.setRedirectPort(connectorEle.attributeValue("redirectPort"));
                    service.getConnectors().add(connector);
                });
                Element engineEle = (Element) serverEle.selectSingleNode("//Engine");
                Engine engine = new Engine();
                engine.setName(engineEle.attributeValue("name"));
                engine.setDefaultHost(engineEle.attributeValue("defaultHost"));
                List<Element> hosts = engineEle.selectNodes("Host");
                engine.setHosts(new ArrayList<Host>());
                hosts.stream().forEach(hostEle->{
                    Host host = new Host();
                    host.setName(hostEle.attributeValue("name"));
                    host.setAppBase(hostEle.attributeValue("appBase"));
                    host.setUnpackWARs(Boolean.getBoolean(hostEle.attributeValue("unpackWARs")));
                    host.setAutoDeploy(Boolean.getBoolean(hostEle.attributeValue("autoDeploy")));
                    host.setContexts(new ArrayList<Context>());

                    List<String> docBases = new ArrayList<>();
                    List<Element> contextEles = hostEle.selectNodes("Context");
                    contextEles.stream().forEach(contextEle->{
                        Context context = new Context();
                        context.setPath(contextEle.attributeValue("path"));
                        context.setDocBase(contextEle.attributeValue("docBase"));
                        context.setReloadable(Boolean.getBoolean(contextEle.attributeValue("reloadable")));
                        context.setWrappers(new ArrayList<Wrapper>());
                        context.setDiskClassLoader(new DiskClassLoader());
                        docBases.add(contextEle.attributeValue("docBase"));
                        host.getContexts().add(context);
                    });

                    URL webappsUrl = this.getClass().getClassLoader().getResource(host.getAppBase());
                    File webapps = null;
                    if(ObjectUtil.isEmpty(webappsUrl)){
                        webapps = FileUtil.file(host.getAppBase());
                    }else{
                        webapps = FileUtil.file(webappsUrl);
                    }
                    if(FileUtil.isNotEmpty(webapps) && webapps.isDirectory()){
                        Arrays.stream(webapps.listFiles()).forEach(web->{
                            if(!docBases.contains(web.getAbsolutePath())){
                                Context context = new Context();
                                context.setPath("/"+web.getName());
                                context.setDocBase(web.getAbsolutePath());
                                context.setReloadable(false);
                                context.setWrappers(new ArrayList<Wrapper>());
                                context.setDiskClassLoader(new DiskClassLoader());
                                host.getContexts().add(context);
                            }
                        });
                    }
                    engine.getHosts().add(host);
                });
                service.setEngine(engine);
                server.getServices().add(service);
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 遍历Host appBase Context所有项目
     * 加载web.xml
     */
    private void initWebapps() {
        server.getServices().stream().forEach(service -> {
            service.getEngine().getHosts().stream().forEach(host -> {
                host.getContexts().stream().forEach(context -> {
                   loadServlet(host, context, context.getDocBase());
                });
            });
        });
    }

    /**
     * 加载解析web.xml，初始化Servlet
     * Context定义，加载path别名+docBase
     * Context未定义，加载appBase所有
     */
    private void loadServlet(Host host, Context context, String docBase) {

        InputStream resourceAsStream = FileUtil.getInputStream(docBase +"/WEB-INF/web.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element =  selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();
                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                Wrapper wrapper = new Wrapper();
                wrapper.setUrlPattern(context.getPath()+servletMapping.selectSingleNode("url-pattern").getStringValue());
                URL pathUrl = this.getClass().getClassLoader().getResource(host.getAppBase());
                Class clazz = null;
                if(ObjectUtil.isNotEmpty(pathUrl) && context.getDocBase().indexOf(FileUtil.file(pathUrl).getAbsolutePath()) == 0){
                    clazz = Class.forName(servletClass);
                }else{
                    clazz = context.getDiskClassLoader().findClass(context, servletClass,context.getDocBase()+classes+ "\\"+StrUtil.replace(servletClass,".","\\")+".class");
                }
                wrapper.setServlet((HttpServlet) clazz.newInstance());
                context.getWrappers().add(wrapper);
            }



        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static ThreadPoolExecutor poolInstance(){
        // 定义一个线程池
        int corePoolSize = 10;
        int maximumPoolSize =50;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler
        );
        return threadPoolExecutor;
    }

    /**
     * Minicat 的程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
